<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.19.0/cytoscape.min.js"></script>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <title>Instagraph</title>
    <style>
        .loading {
            background: linear-gradient(90deg, transparent, #007bff, transparent);
            background-size: 200% 100%;
            animation: loading-animation 2s linear infinite;
        }
        @keyframes loading-animation {
            from {
                background-position: 200% 0;
            }
            to {
                background-position: -200% 0;
            }
        }
    </style>
</head>
<body>
    <div id="app" class="flex flex-col items-start p-6 gap-6 h-screen bg-gray-100">
        <h1 class="sr-only">Instagraph</h1>
        <div v-if="errorMessage !== ''"
             class="w-full h-3 mb-2 bg-red-500 text-white flex items-center justify-between p-4 rounded-md shadow flex">
          <span>[[ errorMessage ]]</span>
          <span class="cursor-pointer" @click="closeErrorMessage">&times;</span>
        </div>
        <form class="flex items-center p-6 gap-6 self-stretch rounded-md bg-white" @submit.prevent="handleFormSubmit">
            <label for="userInput" class="block">
                <span class="text-2xl font-medium">Ask a question</span><br>
                <span class="text-base text-gray-500">Text or URL</span>
            </label>
            <input type="text" class="flex-grow p-2 border rounded-md" v-model="userInput" :disabled="isLoading" />
            <input type="submit" value="Submit" class="py-2 px-4 text-white rounded cursor-pointer"
                   :class="isLoading ? 'bg-blue-200': 'bg-blue-500'" :disabled="isLoading" />
        </form>
        <div class="w-full h-3 bg-white rounded-md" :class="isLoading ? 'loading block' : 'hidden'">
          &nbsp;
        </div>

        <div class="flex flex-col md:flex-row flex-start gap-6 flex-1 self-stretch">
            <div class="flex flex-grow bg-white p-6 rounded-md" ref="cyRef"></div>
            <div class="md:w-1/3 w-full max-w-xs bg-white rounded-md p-6">
                <h2 class="mb-2 text-2xl">History
                  <span @click="renderGraph" class="cursor-pointer">🖊</span>
                </h2>
                <p v-if="historyError !==''"> Error fetching graph history: [[ historyError ]]</p>
                <div class="space-y-2 overflow-y-auto" v-else>
                  <div class="bg-gray-200 hover:bg-gray-300 p-5 rounded" @click="handleGraphItemClick(k)"
                       v-for="(item, k) in graphHistory" :key="k">
                    <p>[[ k + 1 ]]. From: [[ item.from_node.label ]] (Type: [[ item.from_node.type ]]) 
                      - Relationship: [[ item.relationship.type ]] (Direction: [[ item.relationship.direction ]]) 
                      - To: [[ item.to_node.label ]] (Type: [[ item.to_node.type ]])</p>
                  </div>
                </div>
            </div>
        </div>
    </div>
    <script>
      const { createApp, ref, onMounted } = Vue
    
      createApp({
        setup() {
          const isLoading = ref(false);
          const cyRef = ref();
          const userInput = ref("");
          const errorMessage = ref("");

          const graphHistory = ref([]);
          const historyError = ref("");
          
          const calcNodeWidth = (label) => {
            if (label === null || label === undefined) {
              return "50px";
            }
            return Math.max(50, label.length * 8) + "px";
          };

          const renderGraph = async () => {
            try {
              const graphData = await postData("/get_graph_data");
              if (Object.keys(graphData).length === 0) {
                console.error("graphData is empty");
                return;
              } else {
                await createGraph(graphData);
              }
            } catch (error) {
              console.error("Error fetching graph data:", error);
            }
          };
          
          /**
           * Handles form submission, fetches data, and updates the UI.
           *
           * @refactored-by shepherd-06
           *
           */
          async function handleFormSubmit() {
            isLoading.value = true;
            const payload = { user_input: userInput.value };

            try {
              await postData("/get_response_data", payload);
              await renderGraph();
            } catch (error) {
              console.error("Fetch Error:", error);
              showError("Fetch Error: " + error);
            }
            isLoading.value = false;
          }
          
          /**
           * Asynchronously sends a POST request to the specified URL with the given payload.
           *
           * @async
           * @updatedBy shepherd-06
           * @param {string} url - The URL to which the POST request will be sent.
           * @param {Object} [data={}] - The payload that will be sent in the request body, represented as a JavaScript object. Optional.
           *
           * @throws Will throw an error if the response status is not OK.
           * @returns {Promise<Object>} - A Promise that resolves to the JSON-parsed response from the server.
           */
          async function postData(url, data = {}) {
            const response = await fetch(url, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify(data),
            });
            
            if (!response.ok) throw new Error(await response.text());

            return await response.json();
          }

          /**
           * Asynchronously initializes and renders a graph using the Cytoscape library.
           *
           * @async
           * @updatedBy shepherd-06
           * @param {Object} data - An object containing the elements (nodes and edges) for the Cytoscape graph.
           *
           * @example
           * const graphData = {
           *   elements: {
           *     nodes: [...],
           *     edges: [...]
           *   }
           * };
           * await createGraph(graphData);
           *
           * @throws Will throw an error if the Cytoscape initialization fails.
           *
           * @description
           * The function takes in a `data` object, which includes the graph elements like nodes and edges.
           * It then sets up various graph configurations, such as node styles, edge styles, and layout options.
           * Finally, it calls Cytoscape to render the graph within an HTML element with the id "cy".
           */
          async function createGraph(data) {
            cytoscape({
              container: cyRef.value,
              elements: data.elements,
              style: [
                {
                  selector: "node",
                  style: {
                    "background-color": "data(color)",
                    label: "data(label)",
                    "text-valign": "center",
                    "text-halign": "center",
                    shape: "rectangle",
                    height: "50px",
                    width: (ele) => calcNodeWidth(ele.data("label")),
                    color: function (ele) {
                      return getTextColor(ele.data("color"));
                    },
                    "font-size": "12px",
                  },
                },
                {
                  selector: "edge",
                  style: {
                    width: 3,
                    "line-color": "data(color)",
                    "target-arrow-color": "data(color)",
                    "target-arrow-shape": "triangle",
                    label: "data(label)",
                    "curve-style": "unbundled-bezier",
                    "line-dash-pattern": [4, 4],
                    "text-background-color": "#ffffff",
                    "text-background-opacity": 1,
                    "text-background-shape": "rectangle",
                    "font-size": "10px",
                  },
                },
              ],
              layout: {
                name: "cose",
                fit: true,
                padding: 30,
                avoidOverlap: true,
              },
              
              ready: function () {
                this.fit(); // Fits all elements in the viewport
                this.center(); // Centers the graph in the viewport
              },
            });
          }
          
          /**
           * Calculates the optimal text color (either white or black) based on the given background color.
           *
           * @updatedBy shepherd-06
           * @param {string} [bgColor="#000000"] - The background color in hexadecimal format (e.g., "#FFFFFF"). Optional.
           *
           * @returns {string} The optimal text color ("#ffffff" for white or "#000000" for black).
           *
           * @example
           * const textColor = getTextColor("#FFFFFF"); // returns "#000000" (black)
           */
          function getTextColor(bgColor = "#000000") {
            const [r, g, b] = [0, 2, 4].map((start) => parseInt(bgColor.replace("#", "").substr(start, 2), 16));
            const brightness = r * 0.299 + g * 0.587 + b * 0.114;
            return brightness < 40 ? "#ffffff" : "#000000";
          }
          
          /**
           * Asynchronously fetches and displays the graph history from a server endpoint.
           *
           * @async
           * @author shepherd-06
           *
           * @example
           * await fetchGraphHistory();
           *
           * @throws Will log an error to the console if fetching fails or the network response is not ok.
           *
           * @description
           * This function makes an asynchronous GET request to the "/get_graph_history" endpoint to fetch the graph history data.
           * Upon successful retrieval, the function iteratively populates a div element with an id of "history" with the fetched data.
           * If the network response is not ok, an error message will be displayed in the div.
           * If fetching fails, an error will be logged to the console and an error message will be displayed in the div.
           */
          async function fetchGraphHistory() {
            try {
              const response = await fetch("/get_graph_history", {
                method: "GET",
                headers: {
                  "Content-Type": "application/json",
                },
              });

              if (!response.ok) {
                showError("Network response was not ok " + response.statusText);
                return;
              }

              const graph_response = await response.json();
              if (!graph_response.graph) {
                historyError.value = "no graph"
                return;
              }
              graphHistory.value = graph_response.graph_history;
            } catch (error) {
              console.error("Error fetching graph history:", error);
              historyError.value = error;
              return;
            }
          }

          /**
           * Displays an error message in a designated HTML element and hides it after 5 seconds.
           *
           * @author shepherd-06
           *
           * @param {string} message - The error message to be displayed.
           */
          const showError = (message) => {
            errorMessage.value = message;
            setTimeout(() => {
              errorMessage.value = "";
            }, 5000);
          };
          const closeErrorMessage = () => {
            errorMessage.value = "";
          };

          /**
           * Handles a click event on a graph history item and displays it as a graph.
           *
           * @param {key} key - Index of graphHistory
           *
           * @author shepherd-06
           *
           * @example
           * handleGraphItemClick(key);
           *
           * @description
           * This function takes a click event object and retrieves data from the graph history data.
           * It then transforms this data using transformDataToGraphFormat() and displays it using createGraph().
           */
          function handleGraphItemClick(key) {
            const itemData = graphHistory.value[key]
            const transformedData = transformDataToGraphFormat(itemData);
            createGraph(transformedData);
          }

          /**
           * Transforms the graph history data to a format suitable for creating a graph using the createGraph function.
           *
           * @param {Object} itemData - The graph history data.
           *
           * @returns {Object} An object containing elements suitable for the createGraph function.
           *
           * @author shepherd-06
           *
           * @example
           * const transformedData = transformDataToGraphFormat(itemData);
           *
           * @description
           * This function takes an object containing graph history data and transforms it into a format suitable for graph rendering.
           * The transformed object contains nodes and edges with all the necessary attributes.
           */
          function transformDataToGraphFormat(itemData) {
            /**
             * Transforms the graph history data to a format suitable for creating a graph using the createGraph function.
             *
             * @author shepherd-06
             **/
            return {
              elements: {
                edges: [
                  {
                    data: {
                      color: itemData.relationship.color,
                      label: itemData.relationship.type,
                      source: itemData.from_node.id,
                      target: itemData.to_node.id,
                    },
                  },
                ],
                nodes: [
                  {
                    data: {
                      id: itemData.from_node.id,
                      label: itemData.from_node.label,
                      type: itemData.from_node.type,
                      color: itemData.from_node.color,
                    },
                  },
                  {
                    data: {
                      id: itemData.to_node.id,
                      label: itemData.to_node.label,
                      type: itemData.to_node.type,
                      color: itemData.to_node.color,
                    },
                  },
                ],
              },
            };
          }

          onMounted(() => {
            fetchGraphHistory();
          });
          return {
            cyRef,
            userInput,
            isLoading,
            errorMessage,
            graphHistory,
            renderGraph,
            
            handleFormSubmit,
            closeErrorMessage,
            handleGraphItemClick,
            historyError,
          }
        },
        delimiters: ['[[', ']]']

      }).mount('#app')
          
    </script>
  </body>
</html>
